Nightfair

Build the park.
Please the dead.

My contribution

A ghost-filled park, built from the ground up. I create the art, develop the simulation and build the tools that bring it all together.

  • Art direction
  • 3D assets
  • Simulation systems
  • Tools & technical art
Team
Solo project
Development
Ongoing
Engine
Unity / DOTS
Platform
PC
A little tour of the afterlife.0:10 · Silent
About the project

Nightfair is my personal playground for technical art, simulation and tool development. Its ghost-themed amusement park gives each experiment a shared world: somewhere to test an idea and see how it fits with everything else.

It is an ongoing learning project. I am exploring systems and workflows; a playable prototype or game release is not currently planned.

Tools & workflow

Unity · C# · DOTS · Rider
ZBrush · Substance Painter

Project context

Independent solo project. Five months of development, with work continuing. PC performance target: 60+ FPS on an RTX 3060.

It started with the ghosts.

A visual world first. A place to explore art and engineering ever since.

Give it a world.

I built the asset library first: ghosts, scenery and a visual style. The park gave the technical experiments something concrete to grow around.

Make it move.

Crowd behaviour, GPU animation and procedural motion became connected problems, each with its own authoring and debugging tools.

Follow the idea.

The audio experiments developed into ImpulZ. They are now the main focus, moving towards a standalone application beyond Unity.

Room to go deeper.

Some tools solve an immediate need in the park. Others start with a question I want to explore. Working in the same setting lets me connect those ideas instead of leaving them as separate technical demos.

Nightfair gives me room to practise unfamiliar problems, from a ghost's next decision to the way a camera carries momentum or a sound travels through a doorway. The systems below show both the results and the tools I use to inspect them.

Behind the haunted world.

Simulation, animation, tools and sound.
A closer look at how the park works.

Utility AI

A park full of individual decisions.

A data-oriented simulation for 10,000+ active agents, with tools to inspect the crowd and follow the decisions of a single ghost.

The challenge

Complex NPC behaviour becomes expensive at large population sizes. Transform updates and scattered object memory can saturate the main thread before the simulation reaches the intended scale.

How I approached it

I moved agent data into a Structure of Arrays layout and run decisions in Burst-compiled Jobs. A 128-bit filter quickly removes irrelevant intents before the more expensive evaluation work.

Engineering notes

Parallel jobs need a consistent view of agent state. Double buffering gives each simulation step a stable snapshot and avoids race conditions without mutex-based locking.

  • Three behavioural layersSurvival covers physical needs, Reaction handles social influence and emotion, and Strategy represents longer-term habits and memory.
  • SIMD-friendly dataUnmanaged structs support cache prefetching and high instruction throughput. The original project description records an approximately 15× improvement over its traditional C# implementation.
  • Safe pooled referencesGenerational IDs prevent jobs from accidentally querying a recycled entity through a stale reference.
  • Flowfield navigationAgents sample GPU-precalculated vector fields with constant-time lookups instead of running an individual path search.

GPU animation

Animation that runs on the GPU.

A vertex animation texture pipeline that bakes skeletal motion into textures, keeping large animated crowds compatible with GPU instancing.

Animation frames are baked into a position texture and reconstructed by GPU-instanced ghosts. Root motion remains on the CPU in Burst Jobs.

The challenge

Evaluating thousands of skinned meshes consumes CPU time and memory, and prevents the crowd from using a simple instanced rendering path.

How I approached it

I bake vertex motion into relative local-space offsets. The pipeline stores these in 8-bit textures and reconstructs the animation on the GPU, removing runtime skeletal evaluation.

Engineering notes

Low-bit-depth animation can introduce jitter and inaccurate normals. Bounds-based range remapping preserves positional precision, while octahedral encoding stores a normal in two channels for reconstruction in the shader.

  • 8-bit delta posesOffsets relative to the reference pose use the available texture range efficiently and reduce quantization artefacts.
  • Atlas restitchingSeparate VAT textures can be merged into a shared atlas, with bounds renormalization and JSON metadata for runtime lookup.
  • Stable LOD transitionsA custom HLSL screen-door dither uses stable pixel-space noise to avoid flickering as the camera moves.
  • Multi-LOD authoringA non-destructive editor workflow automates multi-resolution baking and range normalization, and supports importing textures from other VAT bakers.

Root motion

Giving floating ghosts a sense of weight.

A motion-authoring tool for assets without runtime skeletal data, using editable curves to add procedural movement and variation.

Six editable motion curves are baked into 256-step lookup tables. CPU Jobs sample the motion while shared playback timing keeps the ghost movement aligned with GPU animation.

The challenge

Instanced VAT characters have no runtime bones to drive their movement. Without a separate motion layer, they can look as though they are gliding through the scene.

How I approached it

I built a procedural motion system around Bézier curves. The authoring tool bakes these curves into 256-step lookup tables so multi-threaded Jobs can sample displacement in constant time.

Engineering notes

CPU movement has to stay synchronized with GPU animation playback. A custom visual interface makes the curve-based system easier to author while keeping the preview aligned with runtime results.

  • Non-destructive modifiersPerlin noise, quantization and time shifts add organic variation to a source motion without modifying the original clip.
  • Efficient trajectoriesA dedicated Bézier evaluation stack supports complex movement curves inside the job pipeline.
  • Visual authoringThe editor provides matching authoring and runtime behaviour, presets, a preset combiner and a six-curve LUT workflow.

Camera & controls

A camera with momentum and intent.

A physics-driven camera for exploring the park, combining a gimbal rig with responsive controls across different viewing distances.

The challenge

Large-scale management cameras can feel detached, and fast movement becomes uncomfortable without suitable damping. Nightfair's world bending adds another reason to give the camera a coherent physical response.

How I approached it

I built a G-force gimbal that uses centripetal acceleration to drive camera lag, shake and field-of-view changes, giving movement a drone-like sense of momentum.

Engineering notes

The camera still needs to respond predictably while carrying momentum. Nonlinear Hermite-spline sensitivity scaling and edge-case snapping keep panning usable at different zoom altitudes.

  • Motion bankingCamera roll blends centripetal acceleration with the player's input direction.
  • Indoor detectionCollision masks and raycasts adjust smoothing and clipping when the camera enters an enclosed space.
  • Gimbal stabilizationWorld-space and local-space up vectors are blended to stabilize the view.
  • Verlet-based dampingMomentum damping adds weight while preserving the direction of the player's input.

Dialogue & localization

Keeping branching conversations connected.

A visual dialogue graph and localization workflow that keep narrative structure, text and translation context together.

The challenge

Large branching narratives make it easy to lose track of dialogue keys, break links or leave translators without the visual context they need.

How I approached it

The dialogue graph serves as the shared source for narrative logic and localization. It compiles into a flat runtime structure, while Translator Vision captures visual context for localization review.

Engineering notes

The graph and its localized text need to stay valid as assets change. A Unicode CLDR rule engine and asset post-processing validate the structure and rich-text formatting.

  • Visual node authoringBranching logic connects to dynamic voice profiles and facial-animation triggers.
  • Translator VisionLocalization audits capture high-resolution visual context for individual dialogue nodes.
  • Allocation-conscious runtimeFlat graph traversal, string formatting and macro replacement are designed to avoid runtime garbage collection.
  • Project-wide validationA QA crawler finds hardcoded strings in binary assets and checks rich-text tags.

Branching history

Keep the paths you did not take.

A branching undo and redo system that preserves alternative edits, allowing tools to revisit different versions of a creative decision.

The challenge

A conventional undo stack discards the redo history as soon as a new edit is made. That makes it difficult to compare alternate audio edits or camera paths without losing earlier work.

How I approached it

The UndoRedoManager stores history as a branching graph. New edits create another branch instead of replacing an existing future, so tools can travel between alternative states.

Engineering notes

Branches increase memory use and can become inconsistent with live state. Serialized delta budgeting prunes the oldest leaf nodes, while state hashes check integrity before restoration.

  • Parallel edit historiesHistory nodes retain alternative timelines so experiments can be revisited.
  • Configurable memory budgetThe system calculates node size and removes old leaf branches when its configured memory limit is exceeded. The source configuration supports budgets of 200 MB and above.
  • Input coalescingRapid operations such as dragging a slider are merged into one action using a 500 ms default window.
  • Context and integritySnapshots preserve UI and viewport context through IUndoContext. Pre-execution hashes detect state drift before an undo is applied.

Debug infrastructure

Make a busy simulation readable.

A shared logging and command layer for inspecting AI, grids, audio and runtime state without losing the source of a diagnostic message.

The challenge

Diagnostic messages from many systems create noise and overhead. Logging wrappers can also obscure the call site, making Unity's double-click source navigation point to the wrapper instead of the real issue.

How I approached it

I built GLog, a dependency-free telemetry layer with atomic bitmask filtering, and a shared console service for commands and simulation inspection. Disabled log categories exit before string formatting or allocation.

Engineering notes

The output needs to stay readable while preserving accurate call stacks. Editor-driven stack-trace configuration hides the logging wrapper and retains full exception traces.

  • Early filteringA 64-bit enabled mask provides constant-time category filtering before messages enter Unity's logging subsystem.
  • Direct source navigationHideInCallstack and StackTraceLogType settings keep ordinary logs connected to their actual source while preserving detailed error traces.
  • Commands without boilerplateReflection discovers methods marked with ConsoleCommand and adds them to a shared registry.
  • Simulation controlsSub-tick stepping, teleportation, a NoClip camera, behavioural inspection, stuck-agent detection and queue-abandonment checks support debugging at crowd scale.

Irregular grid

Organic layouts, connected underneath.

A procedural grid that creates irregular park layouts while retaining a connected structure for simulation and navigation.

The challenge

Square and hexagonal grids can look artificial. Generating irregular chunks separately can also leave mismatched boundaries, gaps and T-junctions.

How I approached it

I combine Lloyd relaxation with Conway Ortho subdivision to create mostly quadrilateral topology with organic cul-de-sacs. A seeded noise frontier keeps independently generated chunk boundaries aligned.

Engineering notes

Asynchronously generated chunks must join reliably. World-space noise blending and positional quantization let the rolling grid identify and weld shared boundary vertices.

  • Two layout algorithmsLloyd relaxation warps regular lattices, while Conway operators subdivide hexagonal layouts into irregular quadrilateral structures.
  • A rolling world gridChunk stitching preserves continuity as new areas are generated and older areas scroll out of view.
  • Navigation from topologyThe same structure supplies the spatial graph for flowfield pathfinding, avoiding a separate baked NavMesh.
  • Parallel generationThe generation pipeline uses Burst and Jobs to build large grids off the main thread. The project description records more than 10,000 cells generated in milliseconds.

ImpulZ Audio Engine

A sound system that grew into its own project.

A custom audio ecosystem for spatial emitters, sound design and mixing, with a timeline, modulation graph, mixer and dedicated diagnostic tools.

The challenge

Dense simulations can turn many concurrent emitters into an indistinct mix while source updates and allocations strain the runtime.

How I approached it

I built a job-driven audio pipeline, moving muffling, air absorption and diffraction work into batched raycasts and worker-thread processing. Authoring separates inexpensive real-time operations from more expensive baked processing so sounds remain editable.

Engineering notes

Moving sources need synchronized playback without repeated allocations. A prewarmed generational pool and HDR-based voice virtualization keep source lifecycles controlled while prioritizing audible content.

  • Sound design and mixingThe authoring tools cover parameter control, envelopes, variants, ducking, profiling, language selection and layers, with timeline, mixer, clip-picker and modulation views.
  • Burst DSP processingSIMD kernels handle parametric EQ, multiple dynamics models and spectral denoising on worker threads. Mid/Side processing and coefficient ramps support detailed edits without abrupt parameter changes.
  • Acoustic propagationSpatial diffraction, occlusion depth, material density and air absorption shape the sound. Rooms, portals and small openings are explored through field visualization and batched raycasts.
  • HDR voice virtualizationA floating loudness window controls the active mix. Virtual voices retain playback position, pitch and envelope state so they can return when their priority increases.
  • A tactile timelineFrame-locked synchronization and spring-based interactions support elastic clip scaling and audio authoring at sub-frame precision.

Listen to the experiments

Best experienced with headphones. Start at a low volume; some samples contain strong transients.

Moving sound

A moving audio object using the internal spatialization engine.

A little sound. A much bigger space.

Compare the original flare-gun recording with the binaural convolution reverb.

Dry signal

The unmodified flare-gun source, before convolution reverb.

Rooms, portals and acoustic diffraction

Beyond the park.

The audio tools are growing into a standalone application built with Rust, Svelte, TypeScript and Tauri. Nightfair's history system, validation tools and audio console provide the starting point.

The next step is to reorganise the code around clear ownership and shared crates, with a runtime that can be integrated into a game engine.